package ua.lviv.franko.solvers.stressanalysis.twoDimention.fem;

import java.util.ArrayList;
import java.util.List;

import org.jzy3d.maths.Range;
import org.jzy3d.plot3d.builder.Mapper;

import ua.lviv.franko.integration.ConstantFunction2D;
import ua.lviv.franko.integration.Gauss2D;
import ua.lviv.franko.integration.IFEMFunction2D;
import ua.lviv.franko.integration.IFunction2D;
import ua.lviv.franko.integration.MatrixFunction2D;
import ua.lviv.franko.integration.VectorFunction2D;
import ua.lviv.franko.solvers.stressanalysis.twoDimention.ElementMatrixForStress;
import ua.lviv.franko.triangulation.SaveStructures.Node;
import ua.lviv.franko.visualisation.Visualise3D;

public class LinearRectangularElement extends AbstractStressElement {

	public static final int				QuadraturePointsNumber	= 20;
	public static final double			EPS						= 0.0000000000001;
	private Gauss2D						gau;

	// rectangle data
	protected Node						A;
	protected Node						B;
	protected Node						C;
	protected Node						D;

	public IFEMFunction2D				N1;
	public IFEMFunction2D				N2;
	public IFEMFunction2D				N3;
	public IFEMFunction2D				N4;

	protected List<Integer>				pos;
	// results
	protected ElementMatrixForStress	data;

	public LinearRectangularElement(final Node A, final Node B, final Node C, final Node D, double e, double v, IFunction2D f1Function, IFunction2D f2Function) {
		super(e, v, f1Function, f2Function);

		pos = new ArrayList<Integer>();
		pos.add(A.Number);
		pos.add(B.Number);
		pos.add(C.Number);
		pos.add(D.Number);

		this.A = A;
		this.B = B;
		this.C = C;
		this.D = D;

		N1 = new IFEMFunction2D() {

			@Override
			public double calculate(double x, double y) {
				if (isInElement(x, y)) {
					return ((x - B.x) * (y - D.y)) / (A.x - B.x) / (A.y - D.y);
				} else {
					return 0.0;
				}
			}

			@Override
			public double calculateDerivatyX(double x, double y) {
				if (isInElement(x, y)) {
					return ((y - D.y)) / (A.x - B.x) / (A.y - D.y);
				} else {
					return 0.0;
				}
			}

			@Override
			public double calculateDerivatyY(double x, double y) {
				if (isInElement(x, y)) {
					return ((x - B.x)) / (A.x - B.x) / (A.y - D.y);
				} else {
					return 0.0;
				}
			}

			@Override
			public double getXi() {
				return A.x;
			}

			@Override
			public double getYi() {
				return A.y;
			}

			@Override
			public Node getNode() {
				// TODO Auto-generated method stub
				return null;
			}
		};
		N2 = new IFEMFunction2D() {

			@Override
			public double calculate(double x, double y) {
				if (isInElement(x, y)) {
					return ((x - A.x) * (y - C.y)) / (B.x - A.x) / (B.y - C.y);
				} else {
					return 0.0;
				}
			}

			@Override
			public double calculateDerivatyX(double x, double y) {
				if (isInElement(x, y)) {
					return ((y - C.y)) / (B.x - A.x) / (B.y - C.y);
				} else {
					return 0.0;
				}
			}

			@Override
			public double calculateDerivatyY(double x, double y) {
				if (isInElement(x, y)) {
					return ((x - A.x)) / (B.x - A.x) / (B.y - C.y);
				} else {
					return 0.0;
				}
			}

			@Override
			public double getXi() {
				return B.x;
			}

			@Override
			public double getYi() {
				return B.y;
			}

			@Override
			public Node getNode() {
				// TODO Auto-generated method stub
				return null;
			}
		};
		N3 = new IFEMFunction2D() {

			@Override
			public double calculate(double x, double y) {
				if (isInElement(x, y)) {
					return ((x - D.x) * (y - B.y)) / (C.x - D.x) / (C.y - B.y);
				} else {
					return 0.0;
				}
			}

			@Override
			public double calculateDerivatyX(double x, double y) {
				if (isInElement(x, y)) {
					return ((y - B.y)) / (C.x - D.x) / (C.y - B.y);
				} else {
					return 0.0;
				}
			}

			@Override
			public double calculateDerivatyY(double x, double y) {
				if (isInElement(x, y)) {
					return ((x - D.x)) / (C.x - D.x) / (C.y - B.y);
				} else {
					return 0.0;
				}
			}

			@Override
			public double getXi() {
				return C.x;
			}

			@Override
			public double getYi() {
				return C.y;
			}

			@Override
			public Node getNode() {
				// TODO Auto-generated method stub
				return null;
			}
		};
		N4 = new IFEMFunction2D() {

			@Override
			public double calculate(double x, double y) {
				if (isInElement(x, y)) {
					return ((x - C.x) * (y - A.y)) / (D.x - C.x) / (D.y - A.y);
				} else {
					return 0.0;
				}
			}

			@Override
			public double calculateDerivatyX(double x, double y) {
				if (isInElement(x, y)) {
					return ((y - A.y)) / (D.x - C.x) / (D.y - A.y);
				} else {
					return 0.0;
				}
			}

			@Override
			public double calculateDerivatyY(double x, double y) {
				if (isInElement(x, y)) {
					return ((x - C.x)) / (D.x - C.x) / (D.y - A.y);
				} else {
					return 0.0;
				}
			}

			@Override
			public double getXi() {
				return D.x;
			}

			@Override
			public double getYi() {
				return D.y;
			}

			@Override
			public Node getNode() {
				// TODO Auto-generated method stub
				return null;
			}
		};

		this.baseFunctions.add(N1);
		this.baseFunctions.add(N2);
		this.baseFunctions.add(N3);
		this.baseFunctions.add(N4);

		gau = new Gauss2D(QuadraturePointsNumber);

	}

	public LinearRectangularElement(final Node A, final Node B, final Node C, final Node D, double k1, double k2, double k3, IFunction2D f1Function, IFunction2D f2Function) {
		super(k1, k2, k3, f1Function, f2Function);

		pos = new ArrayList<Integer>();
		pos.add(A.Number);
		pos.add(B.Number);
		pos.add(C.Number);
		pos.add(D.Number);

		this.A = A;
		this.B = B;
		this.C = C;
		this.D = D;

		N1 = new IFEMFunction2D() {

			@Override
			public double calculate(double x, double y) {
				if (isInElement(x, y)) {
					return ((x - B.x) * (y - D.y)) / (A.x - B.x) / (A.y - D.y);
				} else {
					return 0.0;
				}
			}

			@Override
			public double calculateDerivatyX(double x, double y) {
				if (isInElement(x, y)) {
					return ((y - D.y)) / (A.x - B.x) / (A.y - D.y);
				} else {
					return 0.0;
				}
			}

			@Override
			public double calculateDerivatyY(double x, double y) {
				if (isInElement(x, y)) {
					return ((x - B.x)) / (A.x - B.x) / (A.y - D.y);
				} else {
					return 0.0;
				}
			}

			@Override
			public double getXi() {
				return A.x;
			}

			@Override
			public double getYi() {
				return A.y;
			}

			@Override
			public Node getNode() {
				// TODO Auto-generated method stub
				return null;
			}
		};
		N2 = new IFEMFunction2D() {

			@Override
			public double calculate(double x, double y) {
				if (isInElement(x, y)) {
					return ((x - A.x) * (y - C.y)) / (B.x - A.x) / (B.y - C.y);
				} else {
					return 0.0;
				}
			}

			@Override
			public double calculateDerivatyX(double x, double y) {
				if (isInElement(x, y)) {
					return ((y - C.y)) / (B.x - A.x) / (B.y - C.y);
				} else {
					return 0.0;
				}
			}

			@Override
			public double calculateDerivatyY(double x, double y) {
				if (isInElement(x, y)) {
					return ((x - A.x)) / (B.x - A.x) / (B.y - C.y);
				} else {
					return 0.0;
				}
			}

			@Override
			public double getXi() {
				return B.x;
			}

			@Override
			public double getYi() {
				return B.y;
			}

			@Override
			public Node getNode() {
				// TODO Auto-generated method stub
				return null;
			}
		};
		N3 = new IFEMFunction2D() {

			@Override
			public double calculate(double x, double y) {
				if (isInElement(x, y)) {
					return ((x - D.x) * (y - B.y)) / (C.x - D.x) / (C.y - B.y);
				} else {
					return 0.0;
				}
			}

			@Override
			public double calculateDerivatyX(double x, double y) {
				if (isInElement(x, y)) {
					return ((y - B.y)) / (C.x - D.x) / (C.y - B.y);
				} else {
					return 0.0;
				}
			}

			@Override
			public double calculateDerivatyY(double x, double y) {
				if (isInElement(x, y)) {
					return ((x - D.x)) / (C.x - D.x) / (C.y - B.y);
				} else {
					return 0.0;
				}
			}

			@Override
			public double getXi() {
				return C.x;
			}

			@Override
			public double getYi() {
				return C.y;
			}

			@Override
			public Node getNode() {
				// TODO Auto-generated method stub
				return null;
			}
		};
		N4 = new IFEMFunction2D() {

			@Override
			public double calculate(double x, double y) {
				if (isInElement(x, y)) {
					return ((x - C.x) * (y - A.y)) / (D.x - C.x) / (D.y - A.y);
				} else {
					return 0.0;
				}
			}

			@Override
			public double calculateDerivatyX(double x, double y) {
				if (isInElement(x, y)) {
					return ((y - A.y)) / (D.x - C.x) / (D.y - A.y);
				} else {
					return 0.0;
				}
			}

			@Override
			public double calculateDerivatyY(double x, double y) {
				if (isInElement(x, y)) {
					return ((x - C.x)) / (D.x - C.x) / (D.y - A.y);
				} else {
					return 0.0;
				}
			}

			@Override
			public double getXi() {
				return D.x;
			}

			@Override
			public double getYi() {
				return D.y;
			}

			@Override
			public Node getNode() {
				// TODO Auto-generated method stub
				return null;
			}
		};

		this.baseFunctions.add(N1);
		this.baseFunctions.add(N2);
		this.baseFunctions.add(N3);
		this.baseFunctions.add(N4);

		gau = new Gauss2D(QuadraturePointsNumber);
	}

	protected boolean isInElement(double x, double y) {
		return (A.x - x <= EPS) && (x - B.x <= EPS) && (A.y - y <= EPS) && (y - C.y <= EPS);
	}

	@Override
	public double calculateU(double x, double y, double[] vals) {
		// if (x > B.x || x < A.x || y > C.y || y < A.y) {
		// return 0.0;
		// }
		return N1.calculate(x, y) * vals[pos.get(0) * 2] + N2.calculate(x, y) * vals[pos.get(1) * 2] + N3.calculate(x, y) * vals[pos.get(2) * 2] + N4.calculate(x, y)
				* vals[pos.get(3) * 2];
	}

	@Override
	public double calculateV(double x, double y, double[] vals) {
		// if (x > B.x || x < A.x || y > C.y || y < A.y) {
		// return 0.0;
		// }
		return N1.calculate(x, y) * vals[pos.get(0) * 2 + 1] + N2.calculate(x, y) * vals[pos.get(1) * 2 + 1] + N3.calculate(x, y) * vals[pos.get(2) * 2 + 1] + N4.calculate(x, y)
				* vals[pos.get(3) * 2 + 1];
	}

	@Override
	public double calculateSigma11(double x, double y, double[] vals) {
		double mu = getMuCoef();
		double lmbd = getLjambdaCoef();
		return (mu + lmbd) * calculateDerivatyUX(x, y, vals) + lmbd * calculateDerivatyVY(x, y, vals);

	}

	private double calculateDerivatyVX(double x, double y, double[] vals) {
		return N1.calculateDerivatyX(x, y) * vals[pos.get(0) * 2 + 1] + N2.calculateDerivatyX(x, y) * vals[pos.get(1) * 2 + 1] + N3.calculateDerivatyX(x, y)
				* vals[pos.get(2) * 2 + 1] + N4.calculateDerivatyX(x, y) * vals[pos.get(3) * 2 + 1];
	}

	private double calculateDerivatyVY(double x, double y, double[] vals) {
		return N1.calculateDerivatyY(x, y) * vals[pos.get(0) * 2 + 1] + N2.calculateDerivatyY(x, y) * vals[pos.get(1) * 2 + 1] + N3.calculateDerivatyY(x, y)
				* vals[pos.get(2) * 2 + 1] + N4.calculateDerivatyY(x, y) * vals[pos.get(3) * 2 + 1];
	}

	private double calculateDerivatyUX(double x, double y, double[] vals) {
		return N1.calculateDerivatyX(x, y) * vals[pos.get(0) * 2] + N2.calculateDerivatyX(x, y) * vals[pos.get(1) * 2] + N3.calculateDerivatyX(x, y) * vals[pos.get(2) * 2]
				+ N4.calculateDerivatyX(x, y) * vals[pos.get(3) * 2];
	}

	private double calculateDerivatyUY(double x, double y, double[] vals) {
		return N1.calculateDerivatyY(x, y) * vals[pos.get(0) * 2] + N2.calculateDerivatyY(x, y) * vals[pos.get(1) * 2] + N3.calculateDerivatyY(x, y) * vals[pos.get(2) * 2]
				+ N4.calculateDerivatyY(x, y) * vals[pos.get(3) * 2];
	}

	@Override
	public double calculateSigma12(double x, double y, double[] vals) {
		double mu = getMuCoef();
		return mu * calculateDerivatyVX(x, y, vals) + mu * calculateDerivatyUY(x, y, vals);
	}

	@Override
	public double calculateSigma22(double x, double y, double[] vals) {
		double mu = getMuCoef();
		double lmbd = getLjambdaCoef();
		return (mu + lmbd) * calculateDerivatyVY(x, y, vals) + lmbd * calculateDerivatyUX(x, y, vals);
	}

	public Node getA() {
		return A;
	}

	public ElementMatrixForStress getAll() {

		return data;
	}

	public Node getB() {
		return B;
	}

	public Node getC() {
		return C;
	}

	public Node getD() {
		return D;
	}

	public List<Integer> getPos() {
		return pos;
	}

	@Override
	protected ElementMatrixForStress calculateSmallData(int i, int j) {
		ElementMatrixForStress res = new ElementMatrixForStress(getNodesNumbers());
		res.matrix = this.calculateSmallMatrix(i, j);
		res.vector = this.calculateSmallVector(i);
		return res;
	}

	@Override
	protected double[][] calculateSmallMatrix(int i, int j) {
		double left = A.x, right = B.x, top = C.y, bottom = B.y;

		final IFunction2D fi = baseFunctions.get(i);
		final IFunction2D fj = baseFunctions.get(j);
		return gau.integrate(left, right, bottom, top, new MatrixFunction2D() {

			@Override
			public double[][] calculate(double x, double y) {
				double xDerFi = fi.calculateDerivatyX(x, y);
				double yDerFi = fi.calculateDerivatyY(x, y);
				double xDerFj = fj.calculateDerivatyX(x, y);
				double yDerFj = fj.calculateDerivatyY(x, y);

				return new double[][] { { (xDerFi * xDerFj * k1 + yDerFi * yDerFj * k3), (xDerFj * yDerFi * k2 + yDerFj * xDerFi * k3) },
						{ (xDerFj * yDerFi * k2 + yDerFj * xDerFi * k3), (xDerFi * xDerFj * k3 + yDerFi * yDerFj * k1) } };
			}

			@Override
			public double[][] calculateDerivatyX(double x, double y) {
				return null;
			}

			@Override
			public double[][] calculateDerivatyY(double x, double y) {
				return null;
			}

			@Override
			public int matrixSize() {
				return 2;
			}
		});
	}

	@Override
	protected double[] calculateSmallVector(int i) {
		double left = A.x, right = B.x, top = C.y, bottom = A.y;

		// double[] result = new double[2];

		final IFunction2D fi = baseFunctions.get(i);

		// result[0] = gau.integrate(left, right, bottom, top, new IFunction2D()
		// {
		//
		// @Override
		// public double calculateDerivatyY(double x, double y) {
		//
		// return 0;
		// }
		//
		// @Override
		// public double calculateDerivatyX(double x, double y) {
		//
		// return 0;
		// }
		//
		// @Override
		// public double calculate(double x, double y) {
		// return fi.calculate(x, y) * f1Function.calculate(x, y);
		// }
		// });
		// result[1] = gau.integrate(left, right, bottom, top, new IFunction2D()
		// {
		//
		// @Override
		// public double calculateDerivatyY(double x, double y) {
		//
		// return 0;
		// }
		//
		// @Override
		// public double calculateDerivatyX(double x, double y) {
		//
		// return 0;
		// }
		//
		// @Override
		// public double calculate(double x, double y) {
		// return fi.calculate(x, y) * f2Function.calculate(x, y);
		// }
		// });

		// return result;
		return gau.integrate(left, right, bottom, top, new VectorFunction2D() {

			@Override
			public double[] calculate(double x, double y) {
				double fiValue = fi.calculate(x, y);

				return new double[] { fiValue * f1Function.calculate(x, y), fiValue * f2Function.calculate(x, y) };
			}

			@Override
			public double[] calculateDerivatyX(double x, double y) {
				return null;
			}

			@Override
			public double[] calculateDerivatyY(double x, double y) {
				return null;
			}

			@Override
			public int vectorSize() {
				return 2;
			}
		});
	}

	protected void display(final IFunction2D ff) {
		Visualise3D.show(new Mapper() {
			@Override
			public double f(double x, double y) {
				// return ff.calculateDerivatyY(x, y);//(x, y);
				return ff.calculate(x, y);
			}
		}, new Range(A.x, C.x), new Range(A.y, C.y));
	}

	@Override
	public List<Integer> getNodesNumbers() {

		return getPos();
	}

	public static void main(String[] args) {
		Node A = new Node(-5, -5, true, 0);
		Node B = new Node(100.0, -5, true, 1);
		Node C = new Node(100.0, 100.0, true, 2);
		Node D = new Node(-5, 100.0, true, 3);

		double k1 = 1;
		double k2 = 0.5;
		double k3 = 0.25;

		IFunction2D f1 = new ConstantFunction2D(1);
		IFunction2D f2 = new ConstantFunction2D(1);
		LinearRectangularElement rectangularElement = new LinearRectangularElement(A, B, C, D, k1, k2, k3, f1, f2);

		rectangularElement.display(rectangularElement.baseFunctions.get(0));
	}
}
